"""Uses a dictionary to maintain a data store.

This is a kind of a proxy to the  database, a layer placed between the
GUI and the database to keep from having to read twice the same bunch of
quote data.

You know, in commerce we need inovation. This module has been kept
unchanged for 1 month (today is 29/Mar/2013). Either this Store is lucky to
have a privileged business location, or it is silently losing its customers.

TODO: Really??? Store also has in stock quotes in intervals not recorded in the database, such as 5-min, 10-min
etc
"""
import random
import time

from misc import misc
import infogetter
from misc import timedata
import navalasystem as na
import misc.quoteperiod as qp
import logging
class Store(object):
    dict = {}
    
    def __init__(self):
        self.logger = misc.GetLogger("store")

    def __MakeKey(self, prefix, period, name):
        return "%s|%s|%s" % (prefix, qp.data[period]["varname"], name)
    
    def GetTimeData(self, indicator, name, *args, **kwargs):
        """Returns arbitrary timedata.TimeData object.
        
        Arguments: see __GetTimeData
        
        TODO: I could reorder parameters order to put name before indicator, make indicator optional, add a prefix parameter to replace getting prefix from indicator
        """
        return self.__GetTimeData(indicator._TDClass, indicator.name,
            indicator.GetPeriod(), name, *args, **kwargs)
    
    def GetQuotes(self, period, name, *args, **kwargs):
        """Returns timedata.Quotes object.
        
        Arguments: see __GetTimeData
        """
#        id = random.randint(0, 999999)
#        self.logger.debug("SSSSSSSSSSS %06d gonna get data" % id) # Something is blocking, I want to see how long store takes to get data
#        tic = time.time()
        temp = self.__GetTimeData(timedata.Quotes, "quote", 
            period, name, *args, **kwargs)
#        self.logger.debug("TTTTTTTTTTT %06d got fucking data; this shit took %.5g seconds" % (id, time.time()-tic))
        return temp

    
    def __GetTimeData(self, TDClass, prefix, period, name, dt1=None, dt2=None, numPoints=None):
        """Generic routine to get a TimeData object.
        
        Arguments:
            TDClass -- a timedata.TimeData descendant class object
            prefix -- node prefix, such as "quote", or an Indicator "good name"
            period -- value from within qp
            name -- stock name
            dt1 -- (Optional) Initial datetime or timestamp. Either dt1 or numPoints
                 needs to be specified.
            dt2 -- (Optional) Final datetime or timestamp. Defaults to end of today
            numPoints -- (Optional) Number of data points. Either numPoints or dt1
                         needs to be specified.
                         NOTE: numPoints has preference over dt1.
                         NOTE: it is not guaranteed that numPoints is going to be
                               reached. effectiveNumPoints <= numPoints
                               
                               
        NOTE: Either dt1 or numPoints must be specified.

        Returns:
            A timedata.TimeData object.
        """
#        id = random.randint(0, 999999) # debugging purpose solely
        assert dt1 is not None or numPoints is not None, \
               "Either dt1 or numPoints must be specified!"
        if dt2 is None:
            dt2 = misc.EndOfToday()
        flagFixed = False
        if numPoints is not None:
            # lambda shortcut
            GetTimeDelta = lambda x_precaution: qp.NumPoints2Timedelta(period, numPoints, x_precaution)
            flagFixed = True # Fill try to get a fixed number of points
            MAXNUMTRIES = 17
            precaution = 1.1
            iTry = 0 # tries count, zero-based
            dt1 = dt2-GetTimeDelta(precaution)
            # still have to retry
            
        ts1, ts2 = misc.dt2ts(dt1), misc.dt2ts(dt2) ###IntervalMax(misc.dt2ts(dt2), period)

        key = self.__MakeKey(prefix, period, name)
#        self.logger.debug("%06d Store will return ``%s`` (%s) %s -- %s" % (id, name, qp.data[period]["english"], dt1, misc.ts2dt(ts2)))
        flagRead = False
        if not self.dict.has_key(key):
            flagRead = True
        else:
            ts = self.dict[key].timeData.timestamp
            if len(ts) == 0 or ts[0] > ts1 or ts[-1] <= ts2:
                # Reads if requested interval is out of stored range, or
                # touches latest data point. Latter case accounts for possible 
                # open bars.
                flagRead = True
            
        if flagRead:
#            self.logger.debug("%06d will read again" % id)
            try:
                item = Item()
                lastNumPoints = None
                while True:
###                    (item.timeData, item.ts1, item.ts2) = na.vala.db.td_GetData2(
###                        TDClass, prefix, period, name, dt1, dt2)
                    item.timeData = na.vala.db.td_GetData2(
                        TDClass, prefix, period, name, dt1, dt2)
                    if flagFixed:
                        n = len(item.timeData)
                        if n < numPoints:
                            if iTry < MAXNUMTRIES-1:
                                if lastNumPoints is not None and \
                                    n == lastNumPoints and n > 0:
                                    pass # Got same number of points twice, trying in vain
                                else:
                                    precaution *= 2 # inflates precaution
                                    dt1 = dt2-GetTimeDelta(precaution)
                                    lastNumPoints = n
                                    iTry += 1
                                    self.logger.debug("FIXED NUMBER OF POINTS %d, period %s (((got %d, going for try number %d))" % (numPoints, qp.data[period]["english"], n, iTry))
                                    self.logger.debug("dt1 WAS %s; dt2 WAS %s" % (misc.dt2str(dt1), misc.dt2str(dt2)))
                                    continue
                        else:
                            if n > numPoints:
                                item.timeData = item.timeData[-numPoints:]
                    break
                self.dict[key] = item
                self.logger.debug("In the end, got %d POINTS" % len(item.timeData))
            except:
                self.logger.exception("Error getting data for %s" % name)
                raise
        else:
#            self.logger.debug("%06d data is pwned" % id)
            item = self.dict[key]

        # Eventual slicing
        if flagFixed:
            return item.timeData
        else:
            r = item.timeData.GetTimestampSlice(ts1, ts2)
        self.logger.debug("In the VERY end, got %d POINTS" % len(r))
###        
###        if item.ts1 is None:
###            r = item.timeData
###        else:
###            flagSlice = False
###            if ts1 > item.ts1:
###                flagSlice = True
###                idx1 = misc.BSearchCeil(item.timeData.timestamp, ts1)
###            else:
###                idx1 = 0
###            if ts2 < item.ts2:
###                flagSlice = True
###                idx2 = misc.BSearchFloor(item.timeData.timestamp, ts2)+1
###            else:
###                idx2 = len(item.timeData)
###            if flagSlice:
###                if idx1 == -1 or idx2 == -1:
###                    r = item.timeData.__class__() # Empty result
###                else:
###                    r = item.timeData[idx1:idx2]
###            else:
###                r = item.timeData
###            self.logger.debug("%06d FINISHED - len=%d" % (id, len(r)))
        return r

                

class Item:
    """Store item."""
    timeData = None
    ts1 = None
    ts2 = None
    # This is set to true when quotes is assigned, and externally set to false
    # when the Store receives information about new quotes coming.
    flagValid = False
